package com.ldy.seal.web;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.AcroFields;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.security.BouncyCastleDigest;
import com.itextpdf.text.pdf.security.CertificateInfo;
import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard;
import com.itextpdf.text.pdf.security.PdfPKCS7;
import com.ldy.seal.config.SealConfig;
import com.ldy.seal.util.CacheMap;
import com.ldy.seal.util.CertManager;
import com.ldy.seal.util.PDFUtil;
import com.ldy.seal.util.UKeySealUtil;
import com.ldy.seal.vo.KeywordInfo;
import com.ldy.seal.vo.SealInfo;
import com.ldy.seal.vo.Seals;
import com.nja.util.encoders.Base64;

/**
 * pdf签章预览、签章等操作
 * 
 * @author lindeyi
 *
 */
@RestController
@RequestMapping("/pdf")
@CrossOrigin(allowCredentials = "true", allowedHeaders = "*")
public class PdfController {

	private static Logger logger = LoggerFactory.getLogger(PdfController.class);

	/** 原pdf */
	private static final String SRC_PDF = "src.pdf";
	/** 目标pdf（签章后） */
	private static final String DEST_PDF = "dest.pdf";
	/** PDF文件输出类型 */
	private static final String PDF_TYPE = "application/pdf";
	/** 图片输出类型 */
//	private static final String PNG_TYPE = "image/png";
	private static final String PNG_TYPE = "image/jpeg";

	/** 配置 */
	@Autowired
	private SealConfig config;

	/**
	 * 缓存签章信息
	 */
	public static final Map<String, Seals> CACHE = new CacheMap<>();

	/**
	 * 缓存pdf信息
	 */
	public static final Map<String, PdfSignatureAppearance> CACHE_PDF = new CacheMap<>();

	/**
	 * 缓存pdf摘要信息
	 */
	public static final Map<String, byte[]> CACHE_DIGEST = new CacheMap<>();

	/**
	 * 初始化
	 */
	@PostConstruct
	public void init() {
		Security.addProvider(new BouncyCastleProvider());
	}

	/**
	 * 上传印章
	 * 
	 * @param file         印章图片
	 * @param serialNumber 证书序列号
	 * @return Result
	 */
	@PostMapping("/seal/upload")
	public Result uploadSeal(@RequestParam("file") MultipartFile file, String serialNumber) {
		try {
			File dir = new File(config.getSealSavepath(), serialNumber);
			if (!dir.exists()) {
				dir.mkdirs();
			}

			FileOutputStream fos = new FileOutputStream(new File(dir, Seals.FILE_NAME));
			IOUtils.copy(file.getInputStream(), fos);
			fos.close();
			return Result.success();
		} catch (Exception e) {
			logger.error("", e.getMessage());
		}
		return Result.failure("上传失败");
	}

	/**
	 * 获取ukey印章
	 * 
	 * @param serialNumber 证书序列号
	 * @throws IOException
	 * @return Result
	 */
	@PostMapping("/seal/img")
	public Result sealImg(String serialNumber) throws IOException {
		File dir = new File(config.getSealSavepath(), serialNumber);
		if (!dir.exists()) {
			return Result.failure("没找到图片");
		}
		try (InputStream io = new FileInputStream(new File(dir, Seals.FILE_NAME));
				ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
			IOUtils.copy(io, bos);
			byte[] bytes = bos.toByteArray();
			if (bytes != null) {
				return Result.success(Base64.toBase64String(bytes));
			} else {
				return Result.failure("没找到图片");
			}
		} catch (Exception e) {
			return Result.failure("没找到图片" + e.getMessage());
		}
	}

	/**
	 * 通过关键字获取xy
	 * 
	 * @param id      文档id
	 * @param keyword 关键字
	 * @return Result
	 */
	@PostMapping("/getxy")
	public Result getSealLocation(String id, String keyword) {
		// 模板文件路径
		String inputPdfPath = config.getSavepath() + File.separator + id + File.separator + SRC_PDF;
		try (InputStream io = new FileInputStream(inputPdfPath)) {
			List<KeywordInfo> list = PDFUtil.getSealLocation(new PdfReader(io), keyword);
			return Result.success(list);
		} catch (Exception e) {
			logger.error("", e.getMessage());
			return Result.failure(e.getMessage());
		}
	}

	/**
	 * 上传pdf
	 * 
	 * @param file 上传pdf
	 * @param async 是否异步
	 * @return Result
	 */
	@PostMapping("/upload")
	public Result upload(@RequestParam("file") MultipartFile file, Boolean async) {
		if (null == async) {
			async = false;
		}
		String docmid = getRandomString(10);
		String fileName = config.getSavepath() + File.separator + docmid;
		File f = new File(fileName);
		if (!f.exists()) {
			f.mkdirs();
		}
		try {
			int copy = IOUtils.copy(file.getInputStream(), new FileOutputStream(fileName + File.separator + SRC_PDF));
			System.out.println(copy);
			int pages = PDFUtil.pdf2img(fileName + File.separator + SRC_PDF, fileName, async);
			return Result.success().map("pages", pages).map("id", docmid);
		} catch (Exception e) {
			logger.error("", e.getMessage());
		}
		return Result.failure("上传失败");
	}

	/**
	 * 查看完成签章的pdf
	 * 
	 * @param id       文档id
	 * @param response
	 * @throws IOException
	 */
	@RequestMapping("/pdf/{id}")
	public void pdf(@PathVariable String id, HttpServletResponse response) throws IOException {
		write(config.getSavepath() + File.separator + id + File.separator + DEST_PDF, PDF_TYPE, response);
	}

	/**
	 * 获取文档页数
	 * 
	 * @param id       文档id
	 * @param response
	 * @return Result
	 * @throws IOException
	 */
	@PostMapping("/images")
	public Result images(String id, HttpServletResponse response) throws IOException {

		Assert.hasText(id, "param id is null");
		String fileName = config.getSavepath() + File.separator + id;
		File f = new File(fileName);
		if (!f.exists()) {
			return Result.failure("不存在文件");
		}
		if (new File(fileName, DEST_PDF).exists()) {
			// 已完成签章
			return Result.success().map("finish", true);
		}
		// 求个数
		Long pages = Arrays.asList(f.list()).stream().filter(c -> !(c.endsWith(SRC_PDF) || c.endsWith(DEST_PDF)))
				.count();
		return Result.success().map("finish", false).map("pages", pages);
	}

	/**
	 * 获取pdf指定页图片
	 * 
	 * @param id       文档id
	 * @param index    页码
	 * @param response
	 * @throws IOException
	 */
	@GetMapping("/img/{id}/{page}")
	public void img(@PathVariable String id, @PathVariable String page, HttpServletResponse response)
			throws IOException {
		write(config.getSavepath() + File.separator + id + File.separator + page + ".jpg", PNG_TYPE, response);
	}

	/**
	 * 开始签章（ukey）
	 * 
	 * @param sealsPage 签章信息
	 * @return Result
	 * @throws IOException
	 */
	@PostMapping("/startSeal")
	public Result startSeal(@RequestBody Seals sealsPage) throws IOException {
		// 模板文件路径
		String inputPdfPath = config.getSavepath() + File.separator + sealsPage.getId() + File.separator + SRC_PDF;
		// 图片路径
		byte[] sealImg = sealsPage.getSealImg(config.getSealSavepath());
		Assert.notNull(sealImg, "没有印章图片");
		CACHE.put(sealsPage.getId(), sealsPage);
		// 骑缝章
		SealInfo paging = sealsPage.getPaging();
		// 位置签章
		List<SealInfo> seals = sealsPage.getSeals();

		//
		try (FileInputStream pdfsrc = new FileInputStream(inputPdfPath)) {

			ByteArrayOutputStream pdfout = null;
			InputStream ios = null;
			if (null != paging) {
				// 存在骑缝章
				pdfout = PDFUtil.pagingSeal(pdfsrc, sealImg, paging.getY());
				ios = new ByteArrayInputStream(pdfout.toByteArray());
			} else {
				ios = pdfsrc;
			}
			// 证书转换
			ByteArrayInputStream bio = new ByteArrayInputStream(Base64.decode(sealsPage.getCert()));
			CertificateFactory cf = CertificateFactory.getInstance("X.509");
			X509Certificate certificate = (X509Certificate) cf.generateCertificate(bio);
			int index = 0;
			Certificate[] chain = new Certificate[] { certificate };
			// 摘要算法
			PdfPKCS7 pkcs = new PdfPKCS7(null, chain, "SHA1", null, new BouncyCastleDigest(), false);
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			PdfSignatureAppearance sap = UKeySealUtil.beforeSeal(ios, bos, pkcs, Image.getInstance(sealImg),
					seals.get(index), config.getReason(), null);
			byte[] digest = bos.toByteArray();
			byte[] result = pkcs.getAuthenticatedAttributeBytes(digest, null, null, CryptoStandard.CMS);
			CACHE_DIGEST.put(sealsPage.getId(), digest);
			CACHE_PDF.put(sealsPage.getId(), sap);
			return Result.success().map("complete", false).map("digest", Base64.toBase64String(result)).map("index",
					index);
		} catch (Exception e) {
			logger.error("", e.getMessage());
			return Result.failure(e.getMessage());
		}
	}

	/**
	 * 签名
	 * 
	 * @param id               文档id
	 * @param index            印章序号
	 * @param base64SignedDate 签名后的base64数据
	 * @return Result
	 */
	@PostMapping("/sign")
	public Result sign(String id, int index, String base64SignedDate) {
		// 生成的文件路径
		String outputPdfPath = config.getSavepath() + File.separator + id + File.separator + DEST_PDF;
		try (OutputStream out = new FileOutputStream(outputPdfPath)) {

			Seals seals = CACHE.get(id);
			byte[] sealImg = seals.getSealImg(config.getSealSavepath());
			Assert.notNull(sealImg, "没有印章图片");
			PdfSignatureAppearance appearance = CACHE_PDF.get(id);
			byte[] digest = CACHE_DIGEST.get(id);

			ByteArrayInputStream bio = new ByteArrayInputStream(Base64.decode(seals.getCert()));
			CertificateFactory cf = CertificateFactory.getInstance("X.509");
			X509Certificate certificate = (X509Certificate) cf.generateCertificate(bio);
			Certificate[] chain = new Certificate[] { certificate };
			PdfPKCS7 pkcs = new PdfPKCS7(null, chain, "SHA1", null, new BouncyCastleDigest(), false);
			// 完成签名
			ByteArrayOutputStream os = UKeySealUtil.finishSeal(appearance, pkcs, digest,
					Base64.decode(base64SignedDate));
			index++;
			if (index < seals.getSeals().size()) {
				// 下一个
				ByteArrayOutputStream bos = new ByteArrayOutputStream();
				InputStream ios = new ByteArrayInputStream(os.toByteArray());

				appearance = UKeySealUtil.beforeSeal(ios, bos, pkcs, Image.getInstance(sealImg),
						seals.getSeals().get(index), config.getReason(), null);
				digest = bos.toByteArray();
				byte[] result = pkcs.getAuthenticatedAttributeBytes(digest, null, null, CryptoStandard.CMS);
				CACHE_DIGEST.put(id, digest);
				CACHE_PDF.put(id, appearance);

				return Result.success().map("complete", false).map("digest", Base64.toBase64String(result)).map("index",
						index);
			} else {
				// 完成写出
				out.write(os.toByteArray());
				return Result.success().map("complete", true);
			}
		} catch (Exception e) {
			logger.error("", e.getMessage());
			return Result.failure(e.getMessage());
		}
	}

	/**
	 * 完成签章（后台签章）
	 * 
	 * @param sealsPage 签章信息
	 * @return Result
	 * @throws IOException
	 */
	@PostMapping("/finish")
	public Result finish(@RequestBody Seals sealsPage) throws IOException {
		// 模板文件路径
		String inputPdfPath = config.getSavepath() + File.separator + sealsPage.getId() + File.separator + SRC_PDF;
		// 生成的文件路径
		String outputPdfPath = config.getSavepath() + File.separator + sealsPage.getId() + File.separator + DEST_PDF;
		byte[] sealImg = sealsPage.getSealImg(config.getSealSavepath());
		Assert.notNull(sealImg, "没有印章图片");

		String password = config.getCertPassword(); // keystory密码
		// 骑缝章
		SealInfo paging = sealsPage.getPaging();
		// 位置签章
		List<SealInfo> seals = sealsPage.getSeals();
		try (FileInputStream pdfsrc = new FileInputStream(inputPdfPath);
				OutputStream out = new FileOutputStream(outputPdfPath);
				InputStream p12 = CertManager.getIns().getServiceCert();) {

			ByteArrayOutputStream pdfout = null;
			InputStream ios = null;
			if (null != paging) {
				// 存在骑缝章
				pdfout = PDFUtil.pagingSeal(pdfsrc, sealImg, paging.getY());
				ios = new ByteArrayInputStream(pdfout.toByteArray());
			} else {
				ios = pdfsrc;
			}
			// 位置签章
			pdfout = PDFUtil.seal(ios, p12, password, Image.getInstance(sealImg), seals, config.getReason(), null);
			out.write(pdfout.toByteArray());
			return Result.success();
		} catch (Exception e) {
			logger.error("", e.getMessage());
			return Result.failure(e.getMessage());
		}
	}

	/**
	 * 签章签名
	 * 
	 * @param file 上传待验签pdf
	 * @return Result
	 */
	@PostMapping("/verify")
	public Result verify(@RequestParam("file") MultipartFile file) {
		// 需要生成PDF
		try {
			PdfReader reader = new PdfReader(file.getInputStream());
			AcroFields acroFields = reader.getAcroFields();

			List<String> names = acroFields.getSignatureNames();
			List<Map<String, Object>> signs = new ArrayList<>();
			for (String name : names) {
				Map<String, Object> sign = new HashMap<>();
				PdfPKCS7 pk = acroFields.verifySignature(name);
				sign.put("name", name);
				sign.put("cert", CertificateInfo.getSubjectFields(pk.getSigningCertificate()));
				sign.put("verify", pk.verify());
				signs.add(sign);
			}
			reader.close();
			return Result.success(signs);
		} catch (Exception e) {
			logger.error("", e.getMessage());
			return Result.failure(e.getMessage());
		}

	}

	/**
	 * 模拟获取证书 TODO 当前是测试方法
	 * 
	 * @return Result
	 */
	@PostMapping("/mockCert")
	public Result mockCert() {
		String password = config.getCertPassword(); // keystory密码
		try (InputStream io = CertManager.getIns().getServiceCert()) {

			// 获取证书相关信息
			// 读取keystore
			KeyStore keyStore = KeyStore.getInstance("PKCS12");
			keyStore.load(io, password.toCharArray());
			//
			String alias = keyStore.aliases().nextElement();
			// 获得证书链
			Certificate[] chain = keyStore.getCertificateChain(alias);
			return Result.success(Base64.toBase64String(chain[0].getEncoded()));
		} catch (Exception e) {
			logger.error("", e.getMessage());
			return Result.failure(e.getMessage());
		}
	}

	/**
	 * 模拟签名 TODO 当前是测试方法
	 * 
	 * @param srcDara 签名摘要
	 * @return Result
	 */
	@PostMapping("/mockSign")
	public Result mockSign(String srcDara) {
		String password = config.getCertPassword(); // keystory密码
		try (InputStream io = CertManager.getIns().getServiceCert();) {

			// 获取证书相关信息
			// 读取keystore
			KeyStore keyStore = KeyStore.getInstance("PKCS12");
			keyStore.load(io, password.toCharArray());
			//
			String alias = keyStore.aliases().nextElement();
			// 获得私钥
			PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, password.toCharArray());

			Signature sig = Signature.getInstance("SHA1withRSA");
			sig.initSign(privateKey);
			sig.update(srcDara.getBytes());
			return Result.success(Base64.toBase64String(sig.sign()));
		} catch (Exception e) {
			logger.error("", e.getMessage());
			return Result.failure(e.getMessage());
		}
	}

	/**
	 * 写流
	 * 
	 * @param fileName
	 * @param contentType
	 * @param response
	 * @throws IOException
	 */
	private void write(String fileName, String contentType, HttpServletResponse response) throws IOException {
		response.reset();
		response.setContentType(contentType);
		File file = new File(fileName);
		if (!file.exists()) {
			throw new IOException("签署文件未找到");
		}
		FileInputStream fileInputStream = new FileInputStream(file);
		OutputStream outputStream = response.getOutputStream();
		IOUtils.write(IOUtils.toByteArray(fileInputStream), outputStream);
	}

	/**
	 * 生成随机码
	 * 
	 * @param len
	 * @return String
	 */
	public static String getRandomString(int len) {

		String[] baseString = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g",
				"h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B",
				"C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W",
				"X", "Y", "Z" };
		Random random = new Random();
		int length = baseString.length;
		String randomString = "";
		for (int i = 0; i < length; i++) {
			randomString += baseString[random.nextInt(length)];
		}
		random = new Random(System.currentTimeMillis());
		String resultStr = "";
		for (int i = 0; i < len; i++) {
			resultStr += randomString.charAt(random.nextInt(randomString.length() - 1));
		}
		return resultStr;
	}

}
